package com.demo.lock;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by hunter
 *
 * @Description 一个简单的公平锁demo, 在锁操作的情况下, 之后的请求都会按照顺讯依次处理, 保证公平
 *              具体应用类如 ReentrantLock的公平锁
 *              不公平锁就不举例子了, synchronized 锁住的方法或代码块都算一个非公平锁
 */
public class FairLock {

    // 要被update的数据
    private volatile int data;

    // 要被update的数据 比较值
    private volatile int data2;

    // 等待完成计数器
    private CountDownLatch dataLatch = new CountDownLatch(40);
    private CountDownLatch data2Latch = new CountDownLatch(40);

    // 请求队列
    private BlockingQueue<Thread> queue = new LinkedBlockingQueue<>();

    // 要被unsafe拿来做对比的值
    private volatile int value = 0;

    // unsafe需要的偏移量
    private long valueOffset;

    // jdk api unsafe cas工具
    private Unsafe unsafe;

    {
        try {
            // 暴力获取unsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            // 计算偏移量
            valueOffset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("value"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void startTask() {
        // 要启动一个线程来监听锁和队列并唤醒队列头部线程
        new Thread(() -> {
            while (true) {
                try {
                    // CAS 操作判断锁是否释放
                    while (! unsafe.compareAndSwapInt(this, valueOffset, 0, 1)) {
                    }

                    // 拿出队列头部线程
                    Thread t = queue.take();
                    System.out.println(t.getName() + " is in");

                    // 唤醒 synchronized(t) 是因为t.notify 必须加锁在t上
                    synchronized (t) {
                        t.notify();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 获取锁, 这里将当前线程加入等待队列同时等待
     */
    public void getLock() {
        Thread t = Thread.currentThread();
        queue.add(t);
        try {
            synchronized (t) {
                t.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解锁, 这里将this.value更新为0即可, 因为如果是锁住的, this.value一直是 1, compareAndSwapInt一直在自旋
     * 其实这里应该有个本地变量标识占有锁的线程, 解锁时只有此线程才应该让他解锁
     */
    private void releaseLock() {
        System.out.println(Thread.currentThread().getName() + " is out");
        this.value = 0;
    }

    /**
     * 加锁更新操作
     */
    public void update() {
        getLock();

        data++;

        releaseLock();
    }

    /**
     * 普通更新操作, 用来做比较, 表明锁起到作用了
     */
    public void normalUpdate() {
        data2++;
    }

    public static void main(String[] args) throws InterruptedException {
        FairLock lockTest = new FairLock();
        lockTest.startTask();

        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.update();
                }
                lockTest.dataLatch.countDown();
            }).start();
        }

        // 这里拿普通的++操作来对比, 可以看出是线程不安全的
        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.normalUpdate();
                }
                lockTest.data2Latch.countDown();
            }).start();
        }

        // 等待结束
        lockTest.dataLatch.await();
        lockTest.data2Latch.await();

        // 打印值检测线程安全结果
        System.out.println(lockTest.data);
        System.out.println(lockTest.data2);
    }
}
